<?php

namespace App\Service\Base;


use App\Service\Cache\CacheFile;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Support\Facades\DB;
use App\Service\Log\LogFile;
use App\Service\Cache\BangCache;

// 测试
class Model extends BaseModel
{
    //use HasApiTokens, HasFactory, Notifiable;
    //use HasFactory;

    const CREATED_AT = 'ctime';
    const UPDATED_AT = 'mtime';

    protected $dateFormat = 'U';
    protected $guarded    = [];
    protected $query;

    // 模型读取的时间戳
    protected $casts = [
        //'created_at' => 'date:Y-m-d',           // 日期
        //'deleted_at' => 'datetime:Y-m-d h:i:s'    // 日期时间
        'ctime' => 'timestamp',              // 时间戳
        'mtime' => 'timestamp',              // 日期
    ];

    //
    protected $error;
    protected $inFields;
    protected $outFields;

    // 缓存
    protected $cacheFields = ['id']; // 缓存字段，用于搜索绑定的字段
    protected $bangFields  = ['id'];  // 绑定字段，列表绑定的用于生成缓存的字段
    protected $cacheClass;
    protected $is_cache;
    protected $bang;

    // 初始化
    public function __construct(array $data = [])
    {
        parent::__construct($data);

        // 开启sql日志
        DB::connection()->enableQueryLog();
        // 获取真是表名（在执行数据库操作的时候在来获取）
        // $this->initialize();
    }

    // 初始化(主要用于组件多表查询)
    protected function initialize() { }

    ##########################################################################################
    ## 数据库操作
    // 获取多条数据 不翻页
    public function getList($where = [], $number = 10, $order = "id DESC", $offset = 0)
    {
        // 获取缓存
        $lists    = null;
        $bang     = $this->bang;
        $is_cache = $this->is_cache || $bang;
        $args     = compact('where', 'number', 'order', 'offset');
        if($is_cache){
            $cacheName = $this->cacheKey('lists', $args);
            $lists     = $this->cacheClass()->getCache($cacheName);
        }

        // 字段
        if(is_null($lists)){
            $fields    = $this->inFields;
            $outFields = $this->outFields;
            if(empty($fields)){
                $fields = $this->getTableField(true);
            }
            if(is_string($fields)){
                $fields = explode(",", $fields);
            }
            if($outFields){
                if(is_string($outFields)){
                    $outFields = explode(",", $outFields);
                }
                $fields = array_diff($fields, $outFields);
            }

            // 查询
            $query = $this;
            if(is_array($order)){
            } else{
                $order = explode(" ", $order);
                $query = $query->orderby($order[0], $order[1]);
            }
            if($number){
                $lists = $query->where($where)->offset($offset)->select($fields)->limit($number)->get()->toArray();
            } else{
                $lists = $query->where($where)->select($fields)->get()->toArray();
            }

            // 过滤json
            $jsonField = isset($this->jsonField) ?: '';
            if(!empty($jsonField)){
                $jsonField = is_string($jsonField) ? explode(',', $jsonField) : $jsonField;
                foreach($lists as &$_list){
                    foreach($jsonField as $_field){
                        $_list->$_field = json_decode($_list->$_field, 1);
                    }
                }
            }

            // 保存为缓存
            /*if($is_cache){
                $this->addCacheList($lists, $cacheName, $lists, 'list');
            }*/
            // 用于绑定缓存
            if($is_cache){
                if($lists){
                    $this->addCacheList($lists, $cacheName, $lists, 'list');
                } else{
                    $array_keys = array_keys($where);
                    if(is_string($array_keys[0])){
                        $this->addCacheList([$where], $cacheName, $lists, 'list');
                    }
                }
            }
        }

        return $lists;
    }

    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getListInIds($ids, $fields = ['*'], $pk = 'id')
    {
        // 如果不要缓存 直接查询
        $is_cache = $this->is_cache;
        if(empty($is_cache)){
            $lists = $this->select($fields)->whereIn($pk, $ids)->get()->toArray();
            $lists = array_column($lists, null, $pk);

            return $lists;
        }

        // 如果缓存 则查询缓存
        $lists = [];
        foreach($ids as $id){
            // 获取缓存
            /*$args      = compact('id', 'fields', 'pk');
            $cacheName = $this->cacheKey('getListInIds', $args);
            $cacheData = $this->cacheClass()->getCache($cacheName);
            if(empty($cacheData)){
                // 查询
                $info = $this->where($pk, $id)->select($fields)->first();
                $info = is_object($info) ? $info->toArray() : $info;
                $this->addCacheShow($info, $cacheName, $info);
                $cacheData = $info;
            }
            if($cacheData){
                $lists[$id] = $cacheData;
            }*/

            // 获取数据
            $info = $this->isCache(true)->getOneById($id, $fields, $pk);
            if($info){
                $lists[$id] = $info;
            }
        }

        return $lists;
    }
    // 获取多条数据（参数只有ids，因为使用in没办法缓存，所以这么操作）
    // 获取多条数据（参数只有ids，如果查询某个地方指定会查询指定id的记录，因为id不同，缓存不容易，如果用oneFind则可能查询字段较多，所以创建这个方法）
    public function getOneById($id, $fields = ['*'], $pk = 'id')
    {
        if(empty($id)){
            return [];
        }
        // 如果不要缓存 直接查询
        $getOneById = true;
        $is_cache   = $this->is_cache;
        if(empty($is_cache)){
            $info = $this->select($fields)->where($pk, $id)->first();
            $info = is_object($info) ? $info->toArray() : $info;

            return $info;
        }

        // 如果缓存 则查询缓存
        $args  = compact('id', 'fields', 'pk', 'getOneById');
        $cName = $this->cacheKey('show', $args);
        $info  = $this->cacheClass()->getCache($cName);
        if(empty($info)){
            // 查询
            $info = $this->where($pk, $id)->select($fields)->first();
            $info = is_object($info) ? $info->toArray() : $info;
            $this->addCacheShow($info, $cName, $info);
        }

        return $info;
    }

    // 获取多条数据+翻页
    public function listPage($where = [], $page = 1, $rows = 10, $order = "id DESC")
    {
        // 获取缓存
        $bang     = $this->bang;
        $is_cache = $this->is_cache || $bang;
        $args     = compact('where', 'page', 'rows', 'order');
        if($is_cache){
            $cacheName = $this->cacheKey('lists', $args);
            $result    = $this->cacheClass()->getCache($cacheName);
        }

        // 执行查询
        if(empty($result)){
            // 字段
            $fields    = $this->inFields;
            $outFields = $this->outFields;
            if(empty($fields)){
                $fields = $this->getTableField(true);
            }
            if(is_string($fields)){
                $fields = explode(",", $fields);
            }
            if($outFields){
                if(is_string($outFields)){
                    $outFields = explode(",", $outFields);
                }
                $fields = array_diff($fields, $outFields);
            }

            // 查询  ->recycle()  ->orderby($order)
            if(empty($where)){
                $query = $this->select($fields)->forPage($page, $rows);
                $count = $this->count();
            } else{
                if(is_array($where)){
                    $query = $this->where($where)->select($fields)->forPage($page, $rows);
                    $count = $this->where($where)->count();
                } elseif(is_string($where)){
                    $query = $this->whereRaw($where)->select($fields)->forPage($page, $rows);
                    $count = $this->whereRaw($where)->count();
                }
            }

            if(is_array($order)){
            } else{
                //$order = explode(" ", $order);
                //$query = $query->orderByRaw($order[0], $order[1]);
                $query = $query->orderByRaw($order);
            }

            // 查找过滤
            $is_next = 0;
            $lists   = $query->get()->toArray();
            // 查看是否需要翻页
            if($page * $rows < $count){
                $is_next = 1;
            }

            // 过滤json
            $jsonField = isset($this->jsonField) ?: '';
            if(!empty($jsonField)){
                $jsonField = is_string($jsonField) ? explode(',', $jsonField) : $jsonField;
                foreach($lists as $_list){
                    foreach($jsonField as $_field){
                        $_list->$_field = json_decode($_list->$_field, 1);
                    }
                }
            }

            // 保存为缓存
            $result = ['total' => $count, 'page' => $page, 'rows' => $rows, 'is_next' => $is_next, 'list' => $lists,];
            if($is_cache){
                $this->addCacheList($lists, $cacheName, $result, 'list');
            }
        }

        return $result;
    }

    // 查询一个
    public function oneFind($where, $order = "id DESC")
    {
        // 获取缓存
        $is_cache = $this->is_cache || $this->bang;
        $args     = compact('where', 'order');
        $info     = null;
        if($is_cache){
            $cacheName = $this->cacheKey('show', $args);
            $info      = $this->cacheClass()->getCache($cacheName);
        }

        // 执行查询
        if(empty($info)){
            $query = $this->where($where);
            if(is_array($order)){

            } else{
                $order = explode(" ", $order);
                $query = $query->orderBy($order[0], $order[1]);
            }
            $info = $query->first(); // ->recycle()
            if(empty($info)){
                return $this->return_false('数据不存在！！');
            }

            // 保存为缓存
            if($is_cache){
                $this->addCacheShow($info, $cacheName, $info);
            }
        }

        return $info;
    }

    // 添加
    public function oneAdd($data = [])
    {
        $this->error = '';
        // 验证信息
        $rules  = $this->fieldRule();
        $result = $this->validator($data, $rules, ['id' => '修改的用户编号']);
        if($result === false){
            return false;
        }

        // 模型 和 查询
        //$data = $this->filterNull($data);
        $this->fill($data);
        $result = $this->save();

        //$result = $model->create($data);  // create方法需要制定$fillable 所以这和上面操作是一样的
        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->save($data);

        // 缓存操作
        $this->deleteCacheShow($data);

        return $this->id;
    }

    // 修改
    public function oneEdit($where, $data)
    {
        // 验证信息
        $rules  = $this->fieldRule();
        $result = $this->validator($data, $rules, ['id' => '修改的用户编号']);
        if($result === false){
            return false;
        }

        $info = $this->where($where)->first();  // ->recycle()
        if(empty($info)){
            return $this->return_false('你要修改的数据不存在！！');
        }

        // 保存
        //$data = $this->filterNull($data);
        $info->fill($data);
        $result = $info->save();

        // 或者
        //$data   = $model->fieldArray($data);
        //$result = $model->where($where)->update($data);

        // 缓存操作
        $this->deleteCacheShow($info, $data);

        return $result;
    }

    // 删除
    public function oneDelete($where)
    {
        $info = $this->where($where)->first();  //->recycle()
        if(empty($info)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        // 缓存操作
        $this->deleteCacheShow($info);

        return $info->delete();
    }

    // 执行软删除
    public function oneSoftDelete($where, $field = 'verify')
    {
        $info = $this->where($where)->first();  //->recycle()
        if(empty($info)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        $this->where('id', $info['id'])->update([$field => -1]);

        // 缓存操作
        $this->deleteCacheShow($info);

        return true;
    }


    // 删除
    public function allDelete($ids)
    {
        $list = $this->whereIn('id', $ids)->first();  //->recycle()
        if(empty($list)){
            return $this->return_false('你要删除的数据不存在！！');
        }

        // 缓存操作
        $this->deleteCacheList($list);

        return $list->delete();
    }

    // join操作（暂未完成）
    public function joinList($where, $page, $field, $rows, $orderBy)
    {

        $rand = 'DESC';
        if(is_array($orderBy)){
            $order = $orderBy[0];
            $rand  = $orderBy[1] ?? $rand;
        } else{
            $orderBy = explode(" ", $orderBy);
            $order   = $orderBy[0];
            $rand    = $orderBy[1] ?? $rand;
        }


        $query = $this->from('article model')->where($where)
                      ->join('article_topic as topic', 'topic.article_id', '=', 'article.id')
                      ->join('article_cate as cate', 'cate.article_id', '=', 'article.id');

        if($page){
            // 除了作家以外，都要排除最新三条数据（作家没有顶部置顶三条数据）
            $count    = $query->count();
            $articles = $query->select($field)->orderBy($order, $rand)->forPage($page, $rows)->get()->toArray();
        } else{
            $articles = $query->orderBy('article.id', 'desc')->get()->toArray();
        }


        // 返回
        return $count;
    }

    // 分组返回数据（暂未完成）
    public function onGroupList($number = 1, $orderBy = "a.ctime DESC")
    {
        $rand = 'DESC';
        if(is_array($orderBy)){
            $order = $orderBy[0];
            $rand  = $orderBy[1] ?? $rand;
        } else{
            $orderBy = explode(" ", $orderBy);
            $order   = $orderBy[0];
            $rand    = $orderBy[1] ?? $rand;
        }

        $table  = DB::getTablePrefix().'a';
        $count  = $number + 1;
        $_table = $this->getTable();
        // select count(*) from my8_bracelet_information where uid=a.uid and created_at < a.created_at having Count(*) < 2
        $subSql = $this->selectRaw('count(*)')->whereIn('author_id', $authorIds)->whereIn('a.author_id', $authorIds)
                       ->whereRaw('author_id=la_a.author_id and ctime < la_a.ctime')->havingRaw("count(*) < $count");
        // select a.* from my8_bracelet_information a where exists ($subSql)  order by a.created_at DESC;
        // ->from($articleDb->getTable().' as a')
        $articles = $this->from($_table." as a")->select('a.*')->mergeBindings($subSql->getQuery())
                         ->whereIn('a.author_id', $authorIds)
                         ->whereRaw("exists (".$subSql->toSql().")")->orderBy($order, $rand)->get()->toArray();

        return $articles;
    }

    ##########################################################################################
    ## 字段

    // 获取数据表所有的字段
    public function getTableField($simple = false)
    {
        $cache    = new CacheFile();
        $table    = $this->getTable();
        $tableKey = $table.'_fields';
        $columns  = $cache->store('field')->get($tableKey);
        // 如果没有字段信息 重新在数据库中查询
        if(empty($columns)){
            //DB::enableQueryLog();

            //$fields  = Schema::getColumnListing($table); // 只有字段名称
            $dbName    = DB::getDatabaseName();
            $fulltable = DB::getTablePrefix().$table;
            $field_str = "COLUMN_NAME field,DATA_TYPE `itype`, CHARACTER_MAXIMUM_LENGTH  `length`, ";
            $field_str .= " IS_NULLABLE is_null, COLUMN_DEFAULT `default`, COLUMN_COMMENT comment";
            $where_sql = " WHERE table_name= ? AND  TABLE_SCHEMA=? ";
            $sql       = 'SELECT '.$field_str.' FROM information_schema.columns '.$where_sql;
            $result    = DB::select($sql, [$fulltable, $dbName]);
            $columns   = json_decode(json_encode($result), true);
            $columns   = array_column($columns, null, 'column_name');

            unset($columns['is_recycle']);  // 删除字段 is_recycle
            $cache->store('field')->set($tableKey, $columns);

            // 数据库语句
            //$sqls = DB::getQueryLog();
            //var_dump($sqls);
        }

        // 值需要字段就好
        if($simple){
            $columns = array_column($columns, 'field');
        }

        return $columns;
    }

    // 排除字段
    public function inFields($fields = [])
    {
        $this->inFields = $fields;

        return $this;
    }

    // 查询选择字段
    public function outFields($fields = [])
    {
        $this->outFields = $fields;

        return $this;
    }

    //获取全表名
    public function fullTable()
    {
        $table = $this->getTable();
        $table = DB::getTablePrefix().$table;

        return $table;
    }

    // 制作表单验证
    public function fieldRule()
    {
        // 判断是否有缓存
        $cache    = new CacheFile();
        $table    = $this->getTable();
        $tableKey = $table.'_rules';
        $rules    = $cache->store('fieldRule')->get($tableKey);
        if(!empty($rules)){
            return $rules;
        }

        $rules  = [];
        $fields = $this->getTableField();
        foreach($fields as $_item){
            // 参数
            $_field   = $_item['field'];
            $_itype   = $_item['itype'];
            $_length  = $_item['length'];
            $_default = $_item['default'];
            $_rule    = [];

            // 判断
            if($_field == 'id'){
                continue;
            }

            // 制作验证规则
            if(strpos($_itype, 'int') !== false){
                $_rule[] = 'integer';
            }

            if($_itype == 'tinyint'){
                $_rule[] = 'max:32';
            } elseif($_itype == 'varchar' && $_length){
                $_rule[] = 'max:'.$_length;
            } elseif($_itype == 'text'){
                $_rule[] = 'max:'.$_length;
            }

            // 整合验证规则（如果不存在，则该字段传值则不能为空）
            // $_default
            if(!empty($_rule) && ($_field != 'ip_address')){
                array_unshift($_rule, 'sometimes', 'required');
                $rules[$_field] = $_rule;
            }
        }

        // 缓存
        $cache->store('fieldRule')->set($tableKey, $rules);

        return $rules;
    }

    // 获取自增长值
    public function getAutoInc()
    {
        $table     = $this->getTable();
        $dbName    = DB::getDatabaseName();
        $fulltable = DB::getTablePrefix().$table;
        $doSql     = "SELECT auto_increment FROM information_schema.tables  WHERE TABLE_NAME= ? AND TABLE_SCHEMA = ? ";
        $result    = DB::select($doSql, [$fulltable, $dbName]);

        return $result[0]->auto_increment;
    }

    // 获取需要查询的字段

    ##########################################################################################
    ## 缓存操作
    // 获取缓存
    public function getCache($cacheFile)
    {
        return $this->cacheClass()->getCache($cacheFile);
    }

    // 缓存
    public function isCache($is_cache = true)
    {
        $this->is_cache = $is_cache;

        return $this;
    }

    // 缓存 (中间用.隔开)
    // $bang = ['表名.字段.数字'];
    public function bang($bang = '')
    {
        if(empty($bang)){
            return $this;
        }

        $bang = is_array($bang) ? $bang : [$bang];

        $this->bang = empty($this->bang) ? $bang : array_merge($this->bang, $bang);

        return $this;
    }

    // 缓存用于绑定的字段
    public function bangField($bang = '')
    {
        if(empty($bang)){
            return $this;
        }
        if(is_bool($bang) && ($bang === true)){
            $this->bangFields = $this->cacheFields;
        } else{
            $bang = is_array($bang) ? $bang : [$bang];
            // 重新绑定
            $this->bangFields = empty($this->bangFields) ? $bang : array_merge($this->bangFields, $bang);
            $this->is_cache   = true;
        }

        return $this;
    }

    // 缓存类
    public function cacheClass($store = 'model')
    {
        if(empty($this->cacheClass)){
            $this->cacheClass = (new BangCache())->store($store);
        }

        return $this->cacheClass;
    }

    // 缓存key
    public function cacheKey($key, $param = [])
    {
        return $this->cacheClass()->cacheKey($this->table, $key, $param);
    }

    // 添加详情的缓存
    public function addCacheShow($info, $cacheFileName, $fileData, $old = [])
    {
        // 如果没有需要缓存的内容 或 没有指定绑定文件的时候  不能保存起来
        //（因为没有缓存内容就没有缓存文件，修改的时候就不会更新缓存）
        if(empty($fileData)){
            return true;
        }

        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        //$fields  = $this->getCacheFields();

        // 组合绑定
        //$bang1 = empty($this->bang) ? [] : $this->bang;
        //$bang2 = $cacheMn->bangCacheFileShow($preBang, $fields, $info, $old);
        //$bang  = empty($bang1) ? $bang2 : array_merge($bang1, $bang2);

        // TODO 详情缓存 不用绑定其他的缓存字段
        $info   = is_object($info) ? $info->toArray() : $info;
        $bang   = empty($this->bang) ? [] : $this->bang;
        $bang[] = $preBang.'.id.'.$info['id'].'.show';
        $bang[] = $preBang.'.id.'.$info['id'];

        return $cacheMn->bangCacheAdd($bang, $fileData, $cacheFileName);
    }

    //  检查详情的缓存   如果已经存在，则不处理
    //    避免缓存文件还存在，绑定文件已经被删除
    public function checkBangShow($info, $cacheFileName)
    {
        return $this->addBangShow($info, $cacheFileName);
    }

    //  检查列表缓存   如果已经存在，则不处理
    //    避免缓存文件还存在，绑定文件已经被删除
    public function checkBangList($list, $cacheFileName, $selfBang = 'list')
    {
        return $this->addBangList($list, $cacheFileName, $selfBang);
    }

    // 添加详情的缓存
    public function addBangShow($info, $cacheFileName)
    {
        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        //$fields  = $this->getCacheFields();

        // 组合绑定
        //$bang1 = empty($this->bang) ? [] : $this->bang;
        //$bang2 = $cacheMn->bangCacheFileShow($preBang, $fields, $info, $old);
        //$bang  = empty($bang1) ? $bang2 : array_merge($bang1, $bang2);

        // TODO 详情缓存 不用绑定其他的缓存字段
        $info   = is_object($info) ? $info->toArray() : $info;
        $bang   = empty($this->bang) ? [] : $this->bang;
        $bang[] = $preBang.'.id.'.$info['id'].'.show';
        $bang[] = $preBang.'.id.'.$info['id'];

        return $cacheMn->bangAdd($bang, $cacheFileName);
    }

    // 添加列表缓存
    public function addBangList($list, $cacheFileName, $selfBang = 'list')
    {
        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        $fields  = $this->getBangFields();

        // 组合绑定
        $bang = $cacheMn->bangCacheFileList($preBang, $fields, $list);
        if((count($fields) == 1) && (!is_bool($selfBang))){
            $bang[] = $preBang.'.'.$selfBang;
        }

        return $cacheMn->bangAdd($bang, $cacheFileName);
    }

    // 添加列表缓存
    public function addCacheList($list, $cacheFileName, $fileData, $selfBang = 'list')
    {
        // 如果没有需要缓存的内容 或 没有指定绑定文件的时候  不能保存起来
        //（因为没有缓存内容就没有缓存文件，修改的时候就不会更新缓存）
        /*if(empty($fileData)){
            return true;
        }*/
        // 用于绑定缓存，为空字符也可以绑定
        if(is_null($fileData)){
            return true;
        }

        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        $fields  = $this->getBangFields();

        // 组合绑定
        $bang = $cacheMn->bangCacheFileList($preBang, $fields, $list);
        if((count($fields) == 1) && (!is_bool($selfBang))){
            $bang[] = $preBang.'.'.$selfBang;
        }

        return $cacheMn->bangCacheAdd($bang, $fileData, $cacheFileName);
    }

    // 添加列表缓存
    public function addCacheList2($list, $cacheFileName, $fileData, $selfBang = 'list')
    {
        // 如果没有需要缓存的内容 或 没有指定绑定文件的时候  不能保存起来
        //（因为没有缓存内容就没有缓存文件，修改的时候就不会更新缓存）
        if(empty($fileData)){
            return true;
        }

        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        $fields  = $this->getCacheFields();

        // 组合绑定
        $bang1 = empty($this->bang) ? [] : $this->bang;
        $bang2 = $cacheMn->bangCacheFileList($preBang, $fields, $list);
        if($bang1){
            $bang = array_merge($bang1, $bang2);
        } else{
            $bang = $bang2;
            if(!is_bool($selfBang)){
                $bang[] = $preBang.'.'.$selfBang;
            }
        }

        return $cacheMn->bangCacheAdd($bang, $fileData, $cacheFileName);
    }

    // 删除详情缓存
    // 删除详情的时候，删除缓存字段值的绑定（所以操作删除缓存的时候，要先查询出详情）
    public function deleteCacheShow($info, $old = [])
    {
        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        $fields  = $this->getCacheFields();

        // 组合绑定
        $bang1 = empty($this->bang) ? [] : $this->bang;
        $bang2 = $cacheMn->bangCacheFileShow($preBang, $fields, $info);
        $bang  = empty($bang1) ? $bang2 : array_merge($bang1, $bang2);
        if($old){
            $bang3 = $cacheMn->bangCacheFileShow($preBang, $fields, $old);
            $bang  = array_merge($bang, $bang3);
        }


        // 其他默认绑定
        $bang[] = $preBang.'.list';
        $bang[] = $preBang.'.count';
        $bang[] = $preBang.'.join';
        $bang[] = $preBang.'.tree';

        $cacheMn->bangCacheDelete($bang);
    }

    // 删除列表缓存
    // TODO 几乎很少用到，只有在批量操作时候才有用 或者 某个条件（如某个外键）的删除
    public function deleteCacheList($list)
    {
        $cacheMn = $this->cacheClass();
        $preBang = $this->table;
        $fields  = $this->getCacheFields();

        // 组合绑定
        $bang1 = empty($this->bang) ? [] : $this->bang;
        $bang2 = $cacheMn->bangCacheFileList($preBang, $fields, $list);
        $bang  = empty($bang1) ? $bang2 : array_merge($bang1, $bang2);

        // 其他默认绑定
        $bang[] = $preBang.'.list';
        $bang[] = $preBang.'.count';
        $bang[] = $preBang.'.join';
        $bang[] = $preBang.'.tree';

        $cacheMn->bangCacheDelete($bang);
    }

    // 重新绑定（更新缓存数据）
    public function reSaveCache($id, $updata)
    {
        $cacheMu  = $this->cacheClass();
        $defaults = [$this->table.'.id.'.$id, $this->table.'.id.'.$id.".show", $this->table.'.id.'.$id];
        foreach($defaults as $default){
            $bangFiles = $cacheMu->getBangFile($default);
            if($bangFiles){
                foreach($bangFiles as $_bang){
                    $cacheMu->reCache($_bang, $updata);
                }
                // 重新绑定日志
                (new LogFile())->write($default.':'.var_export($updata, 1), 'resave:bang');
            }
        }
    }

    // 缓存字段
    protected function getCacheFields()
    {
        $fields = $this->cacheFields;
        if(empty($fields)){
            return ['id'];
        }

        if(is_string($fields)){
            $fields = explode(',', $fields);
        }
        if(!in_array('id', $fields)){
            $fields[] = 'id';
        }

        return $fields;
    }

    // 缓存字段
    protected function getBangFields()
    {
        $fields = $this->bangFields;
        if(empty($fields)){
            return ['id'];
        }

        if(is_string($fields)){
            $fields = explode(',', $fields);
        }
        if(!in_array('id', $fields)){
            $fields[] = 'id';
        }

        return $fields;
    }


    // 使用反射 获取当前类方法的缓存文件
    public function getFunctionCacheData($function, $args = [])
    {
        $cache  = $this->cacheKey($function, $args);
        $result = $this->cacheClass()->getCache($cache);

        return $result;
    }

    ##########################################################################################
    ## 使用反射当前模型

    // 使用反射 获取当前类方法的参数
    public function getReflectionParam($function, $all_params = [])
    {
        $args    = [];
        $reflect = new \ReflectionClass(self::class);
        $method  = $reflect->getMethod($function);
        $params  = $method->getParameters();
        foreach($params as $key => $_param){
            $k_name = $_param->getName();
            $v_name = $all_params[$key] ?? $_param->getDefaultValue();
            // 整理
            $args[$k_name] = $v_name;
        }

        return $args;
    }

    // 使用反射 获取当前类方法的缓存文件
    public function getReflectionCacheFile($function, $all_params = [])
    {
        $args    = [];
        $reflect = new \ReflectionClass(self::class);
        $method  = $reflect->getMethod($function);
        $params  = $method->getParameters();
        foreach($params as $key => $_param){
            $k_name = $_param->getName();
            $v_name = $all_params[$key] ?? $_param->getDefaultValue();
            // 整理
            $args[$k_name] = $v_name;
        }

        $cFile = $this->cacheFile($function, $args);

        return $cFile;
    }

    // 利用反射获取缓存
    public function getReflectionCache($function, $all_params = [])
    {
        $info = null;
        $has  = $this->is_cache;
        if($has){
            $args    = [];
            $reflect = new \ReflectionClass(self::class);
            $method  = $reflect->getMethod($function);
            $params  = $method->getParameters();
            foreach($params as $key => $_param){
                $k_name = $_param->getName();
                $v_name = $all_params[$key] ?? $_param->getDefaultValue();
                // 整理
                $args[$k_name] = $v_name;
            }

            $cFile = $this->cacheFile($function, $args);
            $info  = $this->getCache($cFile);
        }

        return $info;
    }


    ##########################################################################################
    ## 验证
    // 某个模型自己的验证
    public function checkData($data, $where = [])
    {
        return $data;
    }

    // 某个模型自己的验证
    protected function checkEdit($data, $id = 0)
    {
        return true;
    }

    // 某个模型自己的验证
    protected function checkAdd($data, $tablename = '')
    {
        $fields = $this->tableFields($tablename);
        $fields = array_keys($fields);

        return true;
    }

    // 某个模型自己的验证
    protected function checkDetele($id)
    {
        return true;
    }

    // 返回错误信息
    protected function return_false($msg = '操作失败！！')
    {
        $this->error = $msg;

        return false;
    }

    // 模型文件里面设置的验证规则（由每个模型文件改写）
    ## 特殊验证规则 +（最小长度，最小值）
    // 枚举：
    // 必填：
    // 邮箱等：
    protected function rules() { return []; }

    // 验证数据
    ## 常用验证规则：
    // 字段类型（过滤操作）：数字，中文，英文
    // 长度：最大长度（过滤操作），最小长度
    // 大小：最大值（过滤操作），最小值
    ## 特殊验证规则 +（最小长度，最小值）
    // 枚举：
    // 必填：
    // 邮箱等：
    public function checkRules($data, $sceneName = 'add')
    {
        // 针对不同的场景筛选验证规则
        $rules = $this->rules();
        $scene = isset($rules[$sceneName]) ? $rules[$sceneName] : [];
        $msg   = isset($rules['msg']) ? $rules['msg'] : [];
        $rule  = isset($rules['rule']) ? $rules['rule'] : [];

        // 验证数据
        ## 常用验证规则：
        // 字段类型（过滤操作）：数字，中文，英文
        // 长度：最大长度（过滤操作），最小长度
        // 大小：最大值（过滤操作），最小值
        ## 特殊验证规则
        // 枚举：
        // 必填：
        // 邮箱等：

        // 如果没有验证规则
        /*if(empty($rule)){
            return true;
        }*/

        // 验证操作
        if(in_array($sceneName, ['add', 'edit'])){
            $result = $this->validator($data, $rule, $msg);
            if($result === false){
                return false;
            }
        }


        // 验证操作
        $result = $this->validator($data, $scene, $msg);
        if($result === false){
            return false;
        }


        return true;
    }

    // 获取错误信息
    public function getError()
    {
        return $this->error;
    }

    // 过滤不存在表中的字段
    public function filterField($data)
    {
        $fields = $this->getTableField(true);
        foreach($data as $key => $val){
            if(!in_array($key, $fields)){
                unset($data[$key]);
            }
        }

        return $data;
    }

    // 获取执行的sql
    public function getSql()
    {
        return DB::getQueryLog();
    }

    // 获取执行的最后一条sql
    public function getLastSql()
    {
        $sqls = DB::getQueryLog();

        return $sqls[count($sqls) - 1] ?? '';
    }

    // 释放sql内存（尤其是命令行模式下，语句都存在内存中，容易溢出）
    public function flushQueryLog()
    {
        DB::flushQueryLog();
    }

    // 表单验证信息
    public function validator($data, $rules = [], $attr = [])
    {
        if(empty($rules)){
            return true;
        }

        // 消息
        $messages = [
            'required'  => ':attribute必须填写！',
            'integer'   => ':attribute必须为整数！',
            'same'      => ':attribute必须与:other相同！！',
            'not_in'    => '所选:attribute无效！',
            'not_regex' => ':attribute格式无效！',
            'numeric'   => ':attribute必须是数字！',
            'password'  => '密码不正确！',
            'image'     => ':attribute必须是图像！',
            'in'        => '选定的:attribute无效！',
            'in_array'  => ':attribute段不存在于:other！',
            'ip'        => ':attribute必须是有效的ip地址！',
            'ipv4'      => ':attribute必须是有效的ipv4地址！',
            'ipv6'      => ':attribute必须是有效的ipv6地址！',
            'json'      => ':attribute必须是有效的json字符串！',
            'unique'    => ':attribute字段已经在数据表中存在！',
            'present'   => ':attribute字段可以为空但必须存在！',

            'required_with'        => '当:values存在时，:attribute必填！',
            'required_with_all'    => '当:values都存在时，:attribute必填！',
            'required_without'     => '当:values不存在时，:attribute必填!',
            'required_without_all' => ':values和:attribute不能同时为空！',

            'size'    => [
                'numeric' => 'The :attribute must be :size！',
                'file'    => 'The :attribute must be :size kilobytes！',
                'string'  => 'The :attribute must be :size characters！',
                'array'   => 'The :attribute must contain :size items！',
            ],

            //'min'       => ':attribute最小长度为:min！',
            //'max'       => ':attribute最大长度为:max！',
            //'between'   => ':attribute长度在:min和:max之间！',
            'min'     => [
                'numeric' => ':attribute必须大于:min！',
                'file'    => ':attribute必须大于:min千字节！',
                'string'  => ':attribute必须长度大于:min！',
                'array'   => ':attribute元素必须大于:min！',
            ],
            'between' => [
                'numeric' => ':attribute必须在:min和:max！',
                'file'    => ':attribute必须在:min千字节和:max千字节之间！',
                'string'  => ':attribute长度必须在:min和:max之间！',
                'array'   => ':attribute元素个数必须在:min和:max之间！',
            ],
            'max'     => [
                'numeric' => ':attribute必须小于:max！',
                'file'    => ':attribute必须小于:max千字节！',
                'string'  => ':attribute长度必须小于:max！',
                'array'   => ':attribute元素个数必须小于:max！',
            ],
            'gt'      => [
                'numeric' => ':attribute必须大于:value！',
                'file'    => ':attribute必须大于:value千字节！',
                'string'  => ':attribute必须大于:value字符数！',
                'array'   => ':attribute必须多于:value个元素！',
            ],
            'gte'     => [
                'numeric' => ':attribute必须大于或等于:value！',
                'file'    => ':attribute必须大于或等于:value千字节！',
                'string'  => ':attribute必须大于或等于:value字符数！',
                'array'   => ':attribute元素个数必须等于或多于:value！',
            ],

            'lt'  => [
                'numeric' => ':attribute必须小于:value！',
                'file'    => ':attribute必须小于:value千字节！',
                'string'  => ':attribute必须小于:value字符数！',
                'array'   => ':attribute元素个数必须小于:value！',
            ],
            'lte' => [
                'numeric' => ':attribute必须小于或等于:value！',
                'file'    => ':attribute必须小于或等于:value千字节！',
                'string'  => ':attribute必须小于或等于:value字符数！',
                'array'   => ':attribute元素个数必须小于或等于:value！',
            ],
            //'sometimes' => '',

            'accepted'        => 'The :attribute must be accepted！',
            'active_url'      => 'The :attribute is not a valid URL！',
            'after'           => 'The :attribute must be a date after :date！',
            'after_or_equal'  => 'The :attribute must be a date after or equal to :date！',
            'alpha'           => 'The :attribute may only contain letters！',
            'alpha_dash'      => 'The :attribute may only contain letters, numbers, dashes and underscores！',
            'alpha_num'       => 'The :attribute may only contain letters and numbers！',
            'array'           => 'The :attribute must be an array！',
            'before'          => 'The :attribute must be a date before :date！',
            'before_or_equal' => 'The :attribute must be a date before or equal to :date！',
            'boolean'         => 'The :attribute field must be true or false！',
            'confirmed'       => 'The :attribute confirmation does not match！',
            'date'            => 'The :attribute is not a valid date！',
            'date_equals'     => 'The :attribute must be a date equal to :date！',
            'date_format'     => 'The :attribute does not match the format :format！',
            'different'       => 'The :attribute and :other must be different！',
            'digits'          => 'The :attribute must be :digits digits！',
            'digits_between'  => 'The :attribute must be between :min and :max digits！',
            'dimensions'      => 'The :attribute has invalid image dimensions！',
            'distinct'        => 'The :attribute field has a duplicate value！',
            'email'           => 'The :attribute must be a valid email address！',
            'ends_with'       => 'The :attribute must end with one of the following: :values！',
            'exists'          => 'The selected :attribute is invalid！',
            'file'            => 'The :attribute must be a file！',
            'filled'          => 'The :attribute field must have a value！',


            'mimes'       => 'The :attribute must be a file of type: :values！',
            'mimetypes'   => 'The :attribute must be a file of type: :values！',
            'multiple_of' => 'The :attribute must be a multiple of :value',


            'regex'           => 'The :attribute format is invalid！',
            'required_if'     => '当:other值为:value时:attribute必传！',
            'required_unless' => 'The :attribute field is required unless :other is in :values！',
            'starts_with'     => 'The :attribute must start with one of the following: :values！',
            'string'          => 'The :attribute must be a string！',
            'timezone'        => 'The :attribute must be a valid zone！',
            'uploaded'        => 'The :attribute failed to upload！',
            'url'             => 'The :attribute format is invalid！',
            'uuid'            => 'The :attribute must be a valid UUID！',
        ];

        // 字段属性
        $attributes = [
            'nickname'          => '姓名',
            'username'          => '用户名',
            'password'          => '登录密码',
            'role'              => '角色',
            'article_no'        => '文章数量',
            'salt'              => '撒盐码',
            'status'            => '用户状态 1-正常 0-禁用',
            'ctime'             => '创建时间',
            'space'             => '广告位',
            'cate_id'           => '栏目ID',
            'image'             => '图片',
            'src'               => '链接',
            'describe'          => '话题描述',
            'author_id'         => '作家ID',
            'title'             => '标题',
            'channel'           => '栏目频道',
            'cover'             => '封面',
            'is_original'       => '是否原创 1-是 0-否',
            'original'          => '来源',
            'video_url'         => '视频链接',
            'duration'          => '音视频时长',
            'read_no'           => '浏览数',
            'virtual_no'        => '虚拟浏览数',
            'comment_no'        => '评论数量',
            'is_top'            => '首页推荐',
            'is_top_cate'       => '栏目页推荐',
            'is_comment'        => '评论权限',
            'is_major'          => '重磅',
            'article_id'        => '文章ID',
            'date'              => '时间',
            'day'               => '日',
            'month'             => '月份',
            'favorite_no'       => '收藏数量',
            'praise_no'         => '点赞数',
            'stamp_no'          => '踩踏数量',
            'share_no'          => '分享数量',
            'read_pc'           => 'pc浏览数',
            'read_ios'          => 'ios浏览数',
            'read_android'      => '安卓浏览数',
            'read_h5'           => 'h5浏览数',
            'virtual_pc'        => 'pc虚拟浏览数',
            'virtual_ios'       => 'iOS虚拟浏览数',
            'virtual_android'   => '安卓虚拟浏览数',
            'virtual_h5'        => 'h5虚拟浏览数',
            'unlock_no'         => '解锁数',
            'uid'               => '',
            'pid'               => '父级',
            'reply_no'          => '回复数量',
            'content'           => '内容',
            'praise'            => '点赞',
            'operate'           => '操作',
            'topic_id'          => '话题ID',
            'ip'                => '用户IP',
            'name'              => '栏目中文名称',
            'avatar'            => '头像',
            'signature'         => '个性签名',
            'en_name'           => '英文名',
            'rank'              => '排序',
            'field'             => '字段',
            'cn_name'           => '中文名',
            'cn_value'          => '中文值',
            'en_value'          => '英文值',
            'keyword'           => '热门词',
            'language'          => '语言类型（1-中文，2-英文）',
            'follow_no'         => '关注人数',
            'virtual_follow_no' => '虚拟关注人数',
            'tag'               => '话题标签',
            'is_propose'        => '是否推荐',
            'residence'         => '居住地',
            'mail_address'      => '邮寄地址',
            'postal'            => '邮编',
            'birthday'          => '生日',
            'edu'               => '教育程度',
            'income'            => '月收入',
            'industry'          => '所处行业',
            'work_scope'        => '工作范畴',
            'company_type'      => '公司类型',
            'worker_no'         => '雇佣人数',
            'mtime'             => '更新时间',
            'email'             => '邮箱',
            'mobile'            => '手机号码 ',
            'head_img'          => '头像',
            'sex'               => '性别 1-男 2-女 0-保密',
            'surname'           => '姓',
            'realname'          => '名字',
            'facebook'          => '脸书openid',
            'twitter'           => '推特ID',
            'last_time'         => '最后登录时间',
            'last_ip'           => '最后登录IP',
        ];

        // 验证
        $attr      = array_merge($attributes, $attr);
        $validator = \Validator::make($data, $rules, $messages, $attr);
        if($validator->fails()){
            $messages    = $validator->errors();
            $this->error = $messages->first();

            return false;
        }

        return true;
    }
}


